package org.liziyi.pss.model.schedule;

import org.liziyi.pss.common.enums.ProcessStatusEnum;
import org.liziyi.pss.common.enums.ScheduleCallbackEnum;
import org.liziyi.pss.config.Config;
import org.liziyi.pss.entity.CpuCore;
import org.liziyi.pss.entity.ProcessControlBlock;
import org.liziyi.pss.common.enums.QueueEnum;
import org.liziyi.pss.gui.QueueGroupGUI;
import org.liziyi.pss.model.Memory;
import org.liziyi.pss.model.Scheduler;
import org.liziyi.pss.model.cpu.Cpu;
import org.liziyi.pss.model.memory.FirstAdaptMemory;
import org.liziyi.pss.model.queue.*;
import org.liziyi.pss.util.ProcessUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static org.liziyi.pss.common.enums.ScheduleCallbackEnum.*;


/**
 * 进程调度者
 *
 * @author liziyi
 */
public class MainScheduler implements Scheduler {
    private final Cpu cpu;
    private final Memory memory;
    private final ScheduleQueueGroup scheduleQueueGroup;
    /**
     * 负责将进程从后备队列移动到就绪队列，并分配内存资源
     */
    private Thread scheduleProcessFromBackupQueueThread;
    /**
     * 负责从就绪队列中获取进程并分配资源交由CPU执行
     */
    private Thread scheduleProcessFromReadyQueueThread;
    /**
     * 用于进度条刷新的线程池
     */
    private ExecutorService threadPool;
    /**
     * 用于回调的函数，回调的场景主要是进程状态变更时，改变可挂起，可解挂进程列表对应的JComboBox
     */
    private final Map<ScheduleCallbackEnum,Consumer<Integer>> callbackMap;
    public MainScheduler(){
        cpu=new Cpu();
        memory = new FirstAdaptMemory();
        QueueGroupGUI queueGroupGUI=new QueueGroupGUI();
        scheduleQueueGroup = new ScheduleQueueGroup(queueGroupGUI,new HashMap<>(Map.of(
                QueueEnum.BACKUP_QUEUE,new BackUpQueue(queueGroupGUI.getBackupQueueJTable().getModel()),
                QueueEnum.READY_QUEUE,new ReadyQueue(queueGroupGUI.getReadyQueueJTable().getModel()),
                QueueEnum.SUSPEND_QUEUE,new SuspendQueue(queueGroupGUI.getSuspendQueueJTable().getModel()),
                QueueEnum.FINISH_QUEUE,new FinishQueue(queueGroupGUI.getFinishQueueJTable().getModel())
        )));
        threadPool= Executors.newFixedThreadPool(Config.cpuCore+2);
        callbackMap=new HashMap<>();
    }

    public Cpu getCpu() {
        return cpu;
    }

    public Memory getMemory() {
        return memory;
    }

    public ScheduleQueueGroup getScheduleQueueGroup() {
        return scheduleQueueGroup;
    }

    @Override
    public void schedule() {
        if(scheduleProcessFromBackupQueueThread!=null || scheduleProcessFromReadyQueueThread!=null){
            return;
        }
        // 该线程负责从后备队列中获取就绪进程移动到就绪队列
        scheduleProcessFromBackupQueueThread=new Thread(()->{
            while(!scheduleProcessFromBackupQueueThread.isInterrupted()){
                ProcessControlBlock pcb;
                // 后备队列中存在进程可以移动到就绪队列，则进行调度（注意，由于同步进程的存在，队列非空也有可能没有进程能被移动到就绪队列）
                if((pcb=scheduleQueueGroup.pollElement(QueueEnum.BACKUP_QUEUE))!=null){
                    int pid=pcb.getPid();
                    System.out.printf("进程{%d}等待分配内存，所需大小:%d...\n",pid,pcb.getMemoryNeed());
                    int[] allocatedMemory;
                    while((allocatedMemory=memory.allocate(pcb.getMemoryNeed(),pid))==null){
                        try {
                            // 获取内存失败，稍后重试
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                    System.out.printf("进程{%d}内存分配完成，地址：[%d,%d]\n",pid,allocatedMemory[0],allocatedMemory[1]);
                    pcb.setMemoryUsed(allocatedMemory);
                    // 后备队列中进程分配好内存后移入可挂起进程对应的JComboBox
                    callbackMap.get(SCHEDULE_FROM_BACKOP_TO_READY).accept(pid);
                    // 后备队列中进程分配好内存后移入就绪队列
                    pcb.setState(ProcessStatusEnum.READY.toString());
                    scheduleQueueGroup.addElement(QueueEnum.READY_QUEUE,pcb);
                }else{
                    try {
                        // 后备队列为空，稍后重试
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        },"scheduleProcessFromBackupQueueThread");
        // 该线程负责从就绪队列中获取进程并交由CPU执行
        scheduleProcessFromReadyQueueThread=new Thread(()->{
            while(!scheduleProcessFromReadyQueueThread.isInterrupted()){
                // 就绪队列不为空就需要调度
                ProcessControlBlock pcb;
                if((pcb=scheduleQueueGroup.pollElement(QueueEnum.READY_QUEUE))!=null){
                    CpuCore core;
                    try{
                        core=cpu.getFreeCore();
                    }catch (InterruptedException ex){
                        return;
                    }
                    core.setPid(pcb.getPid());
                    CompletableFuture.supplyAsync(()->{
                        cpu.execTask(pcb,core);
                        pcb.setPriority(pcb.getPriority()+1);
                        pcb.setTimeSlice(pcb.getTimeSlice()-1);
                        return pcb;
                    },threadPool).whenComplete((res,ex)->{
                        // 该进程已完全执行完毕,移入完成队列
                        if(res.getTimeSlice()==0){
                            memory.free(res.getMemoryUsed());
                            scheduleQueueGroup.addElement(QueueEnum.FINISH_QUEUE,res);
                            callbackMap.get(SCHEDULE_FROM_RUNNING_TO_FINISH).accept(res.getPid());
                        } else{  // 返回就绪队列，等待重新调度
                            res.setState(ProcessStatusEnum.READY.toString());
                            scheduleQueueGroup.addElement(QueueEnum.READY_QUEUE,res);
                        }
                    });
                }else{
                    try {
                        // 就绪队列为空，稍后重试
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        return;
                    }
                }
            }
        },"scheduleProcessFromReadyQueueThread");
        scheduleProcessFromBackupQueueThread.start();
        scheduleProcessFromReadyQueueThread.start();
    }

    @Override
    public void addProcess(ProcessControlBlock pcb) {
        scheduleQueueGroup.addElement(QueueEnum.BACKUP_QUEUE,pcb);
    }

    public void suspendProcess(int pid) {
        ProcessControlBlock pcb=scheduleQueueGroup.removeElement(QueueEnum.READY_QUEUE,pid);
        if(pcb==null) return;
        pcb.setState(ProcessStatusEnum.SUSPEND.toString());
        scheduleQueueGroup.addElement(QueueEnum.SUSPEND_QUEUE,pcb);
        callbackMap.get(SCHEDULE_FROM_READY_TO_SUSPEND).accept(pcb.getPid());
    }

    public void unSuspendProcess(int pid) {
        ProcessControlBlock pcb=scheduleQueueGroup.removeElement(QueueEnum.SUSPEND_QUEUE,pid);
        pcb.setState(ProcessStatusEnum.READY.toString());
        scheduleQueueGroup.addElement(QueueEnum.READY_QUEUE,pcb);
        callbackMap.get(SCHEDULE_FROM_SUSPEND_TO_READY).accept(pcb.getPid());
    }

    @Override
    public void reset() {
        // 线程资源重置
        scheduleProcessFromReadyQueueThread.interrupt();
        scheduleProcessFromBackupQueueThread.interrupt();
        threadPool.shutdownNow();
        scheduleProcessFromBackupQueueThread=null;
        scheduleProcessFromReadyQueueThread=null;
        threadPool= Executors.newFixedThreadPool(Config.cpuCore+2);
        // 内存重置
        memory.reset();
        // 队列重置
        scheduleQueueGroup.reset();
        // cpu重置
        try {
            TimeUnit.MILLISECONDS.sleep(30);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        cpu.reset();
        // pid计数重置
        ProcessUtil.resetPid();
        // 回调清除
        callbackMap.clear();
    }


    /**
     * 调度相关回调注册
     * @param consumer
     */
    public void addScheduleListener(ScheduleCallbackEnum scheduleCallbackEnum,Consumer<Integer> consumer){
        if(callbackMap.containsKey(scheduleCallbackEnum)){
            return;
        }
        callbackMap.put(scheduleCallbackEnum,consumer);
    }
}
